package edu.umd.ion.wavemeter.server;

import edu.umd.ion.wavemeter.server.PIDControl.ChannelVoltage;
import edu.umd.ion.wavemeter.service.CalibrationResult;
import edu.umd.ion.wavemeter.service.ChannelEvent;
import edu.umd.ion.wavemeter.service.ChannelRoutingInfo;
import edu.umd.ion.wavemeter.service.PIDSettingsInUse;
import edu.umd.ion.wavemeter.service.ServerParameters;
import edu.umd.ion.wavemeter.service.InterlockSettings;
import edu.umd.ion.wavemeter.service.PIDSettings;
import edu.umd.ion.wavemeter.service.PatternData;
import edu.umd.ion.wavemeter.service.PatternRequest;
import edu.umd.ion.wavemeter.service.WavemeterChannelData;
import edu.umd.ion.wavemeter.service.WavemeterData;
import edu.umd.ion.wavemeter.service.WavemeterException;
import edu.umd.ion.wavemeter.service.WavemeterParameters;
import edu.umd.ion.wavemeter.service.WavemeterSetException;

import java.util.Date;
import java.util.TreeMap;
import java.util.Vector;
import java.util.concurrent.TimeUnit;

/**
 * PollingThread contains the code calling the WavemeterLibrary function
 * to set properties of the Wavemeter and handles the callback notifications
 * from the Wavemeter. This class should be used as static in order to have
 * only one instance keeping track of the Wavemeter status. The current
 * values are stored and can be retrieved by multiple threads.
 * 
 * @author monroe-lab
 *
 */
public class PollingThread extends Thread implements PollingThreadInterface {
	
	private class InternalPatternRequest {
		public int PatternMask;
		public int AveragePoints;
		public long Time;
	}
	private final int NumVirtChannels = WavemeterConfiguration.NumVirtChannels();
	private final int NumRawChannels = WavemeterConfiguration.NumRawChannels();
	
	/**
	 *  timeout in ms. If timeout ms passed without a client querying the status of
	 *  the Wavemeter, the callback is released.
	 */
	final long ClientCallTimeout = Long.MAX_VALUE; //20000;
	
	/**
	 *  Timeout for the pattern garbage collector. If no client reads a pattern with index i
	 *  for this time, the pattern export is disabled.
	 */
	final long PatternRequestTimeout = 50000;
		
	/**
	 *  The current status of the Wavemeter with the last measurement results.
	 */
	private WavemeterData Data = new WavemeterData( NumVirtChannels );
	 
	/**
	 *  The pattern Request bookkeeping data 
	 */
	private InternalPatternRequest[] PatternRequestList;

	/**
	 *  Bookkeeping of the pattern indices which are enabled
	 */
	public long[] LastPatternRead = new long[WavemeterParameters.NumPatterns];
	
	/**
	 *  The copy of the patterns to be returned to clients.
	 */
	private TreeMap<Integer,PatternData> PatternMap
		= new TreeMap<Integer,PatternData>();
	
	/**
	 *  The last time the wavemeter switched channels.
	 */
	int LastSwitching;
	
	/**
	 *  The last time all the wavemeter data was polled.
	 */
	long LastDataPoll;
	
	/**
	 *  The last time a client requested the Wavemeter status.
	 */
	long LastClientCall;
	
	/**
	 *  true if the thread should terminate as soon as possible.
	 */
	boolean Shutdown = false;
	
	/** 
	 *  Index of the last Raw Channel measured
	 */
	int LastMeasuredChannel;
	
	/**
	 *  Number of active Raw Channels
	 */
		
	ChannelManager ChannelManager;
	VirtChannelData[] VirtChannel;
	ExposureControl MyExposureControl;
	PIDControl MyPIDControl;
    LogTemperature MyLogTemperature;
	
	int OperationMode = WavemeterLibrary.cCtrlStopAll;
	
	
	/**
	 *  Constructor: Query the wavemeter data
	 */
	public PollingThread()
	{
		MyExposureControl = WavemeterConfiguration.ExposureControl(Data);
		ChannelManager = WavemeterConfiguration.ChannelManager(this, Data, MyExposureControl);
		MyPIDControl = WavemeterConfiguration.PIDControl(Data,ChannelManager);
		LastClientCall = System.currentTimeMillis();
		System.out.println((new Date()).toString()+" PollingThread: Initialization");
		System.out.flush();
		PatternRequestList = new InternalPatternRequest[NumVirtChannels];
		VirtChannel = new VirtChannelData[NumVirtChannels];
		MyExposureControl = new DynamicExposureControl(Data);
		for (int i=0; i<NumVirtChannels; ++i) {
			PatternRequestList[i] = new InternalPatternRequest();
			VirtChannel[i] = new VirtChannelData( new RunnableTwo<Integer, Integer>() {
				@Override
				public void run(Integer channel, Integer Serial) {
					Data.ChannelData[channel].InterlockSettingsSerial = Serial;
				}				
			});
			double Min=0, Max=0;
			VirtChannel[i].setInterlock( new InterlockSettings(i,Min,Max) );
		}
		MyLogTemperature = new LogTemperature();
		Data.CalibrationResultsSerial = 1;
	}
	/**
	 *  cleanup is called before the run() function returns.
	 */
	synchronized public void cleanup()
	{
		System.out.println((new Date()).toString()+" PollingThread: Cleanup");
		try {
			setMeasurement(false);
			ChannelManager.cleanup();
			MyPIDControl.cleanup();
		} catch (Exception ex) {}
		System.out.flush();
	}
	/**
	 *  tells the thread to terminate at the next possible time.
	 */
	synchronized public void expire()
	{
		Shutdown = true;
		System.out.println((new Date()).toString()+" Server Shutdown requested");
		System.out.flush();
	}
	
	synchronized private boolean keepRunning() {
		return !Shutdown && System.currentTimeMillis()-LastClientCall < ClientCallTimeout;
	}
	/**
	 *  Tell the thread that the client is still listening.
	 *  Sets the time of the last client call to the current time.
	 * @return Thread.isAlive()
	 */
	synchronized public boolean keepAlive()
	{
		LastClientCall = System.currentTimeMillis();
		return isAlive();
	}
	static public int patternCode(int Channel, int Pattern)
	{
		return Channel<<8 | (Pattern & 0xff); 
	}
	static public int patternMask(int Pattern)
	{
		return 1<<Pattern;
	}

	/**
	 * Get all available Patterns. Gets the container with all currently available
	 * patterns. The calling function should just copy the patterns it is interested in.
	 * @return container of currently available patterns.
	 */
	synchronized public Vector<PatternData> getPatterns(Vector<PatternRequest> Request) throws WavemeterSetException
	{
		Vector<PatternData> MatchingPatterns = new Vector<PatternData>();
		for (int i=0; i<Request.size(); ++i) {
			PatternRequest Current = Request.get(i);
			setPattern(Current);
			int PatternCode = patternCode(Current.Channel,Current.Pattern);
			// Do we have something interesting
			PatternData ThisData = PatternMap.get(PatternCode);
			if (ThisData!=null && ThisData.Timestamp>Current.LastTimestamp+Current.Delay) {
				MatchingPatterns.add(ThisData);
			}
			// Register the next request
			InternalPatternRequest CurrentInternalRequest = PatternRequestList[Current.Channel];
			int CurrentMask = patternMask(Current.Pattern);
			// figure out the next time to copy patterns
			if (CurrentInternalRequest.PatternMask==0) {
				CurrentInternalRequest.Time = System.currentTimeMillis()+Current.Delay;
				CurrentInternalRequest.AveragePoints = Current.AveragePoints;
			}
			else {
				if (CurrentInternalRequest.Time>System.currentTimeMillis()+2*Current.Delay)
				CurrentInternalRequest.Time = System.currentTimeMillis()+Current.Delay;
				CurrentInternalRequest.AveragePoints = Math.min(Current.AveragePoints,CurrentInternalRequest.AveragePoints);
			}
			// Set the bitmask
			CurrentInternalRequest.PatternMask |= CurrentMask;
		}
		return MatchingPatterns;
	}
	/**
	 * Enables the tracing of a specific pattern. This function must be called
	 * before patterns can be received with getPatterns()
	 * @param pattern to be enabled/disabled
	 * @throws WavemeterSetException
	 */
	synchronized public void setPattern(PatternRequest pattern) throws WavemeterSetException
	{
		if (LastPatternRead[pattern.Pattern]==0 && Data.ChannelData[pattern.Channel].active)
		{
			int result = WavemeterLibrary.SetPattern(pattern.Pattern, WavemeterLibrary.cPatternEnable );
			if (result!=0)
					throw new WavemeterSetException("SetPattern",result);
			LastPatternRead[pattern.Pattern]=System.currentTimeMillis();
			System.out.println((new Date()).toString()+" Enable Pattern "+pattern.Pattern);
		}	
	}
	/**
	 *  Disable patterns which were not read by a client for a while and thus were not copied
	 *  for the same while.
	 */
	synchronized protected void patternGarbageCollector()
	{
		if (System.currentTimeMillis() - LastDataPoll > 10000) {

			for (int i=0; i<WavemeterParameters.NumPatterns; ++i)
			{
				long Last = LastPatternRead[i];
				if (Last!=0 && (Shutdown || Last+PatternRequestTimeout<System.currentTimeMillis()))
				{
					WavemeterLibrary.SetPattern(i, WavemeterLibrary.cPatternDisable );
					LastPatternRead[i]=0;		
					System.out.println((new Date()).toString()+" Disable Pattern "+i);
				}
			}
		}
	}
	synchronized InterlockSettings getInterlock(int channel) {
		return VirtChannel[channel].getInterlock();
	}
	
	synchronized InterlockSettings setInterlock(InterlockSettings settings) {
		VirtChannel[settings.Channel].setInterlock(settings);
		Data.ChannelData[settings.Channel].InterlockEnabled = settings.Enable;
		Data.ChannelData[settings.Channel].InterlockSettingsSerial = settings.Serial;
		if (!settings.Enable) {
			ChannelManager.pushInterlock(settings.Channel, settings.Enable && 
					!Data.ChannelData[settings.Channel].InterlockInRange);
		} else {
			Data.ChannelData[settings.Channel].InterlockInRange = true;
		}
		return settings;
	}
	
	synchronized void addChannelName(int Channel, String Name) {
		VirtChannel[Channel].Names.add(Name);
	}

	synchronized ServerParameters getExposureParameters() {
		ServerParameters PC = ChannelManager.getParameters();
		ServerParameters PE =MyExposureControl.getParameters();
		PE.UnderexposedTimeout = PC.UnderexposedTimeout;
		return  PE;
	}

	synchronized ServerParameters setExposureParameters(ServerParameters param) {
		ChannelManager.setParameters(param);
		return MyExposureControl.setParameters(param);
	}
	
	synchronized public void clearPIDHistory(int channel) throws WavemeterException {
		MyPIDControl.clear(channel);
	}
	
	/**
	 * Get all PID settings for the given Channel
	 * @param Channel
	 * @return PIDSettings
	 */
	synchronized PIDSettings getPID(int Channel){
		return MyPIDControl.getPID(Channel);
	}
	
	
	public PIDSettingsInUse getPIDSettingsInUse() throws WavemeterException {
		return MyPIDControl.getPIDSettingsInUse();
	}

	/**
	 * Set all PID settings for the given Channel
	 * @param PIDSettings
	 * @return PIDSettings as reported from the WavemeterLibrary after the new settings were applied.
	 * @throws WavemeterException 
	 */ 
	synchronized PIDSettings setPID( PIDSettings PID ) throws WavemeterException
	{
		PIDSettings s =  MyPIDControl.setPID(PID);
		MyPIDControl.enableRegulation(PID.Channel, PID.LockEnabled, new RunnableTwo<ChannelVoltage, Double>() {
			public void run(ChannelVoltage o, Double setpoint) {
				Data.ChannelData[o.Channel].AOVoltage = o.Voltage;
				Data.ChannelData[o.Channel].PIDSetWavelength = setpoint;
			}
		});
		return s;
	}

	
	synchronized void registerPIDCourseOracle(PIDCourseOracle oracle) {
		MyPIDControl.registerCourseOracle(oracle);
	}

	/**
	 * Set the PID Course for a given Channel
	 * @param Course
	 * @return the Course as reported from the WavemeterLibrary after the new Course is applied.
	 * @throws WavemeterException 
	 */
	synchronized String setPIDCourse( int Channel, String Course ) throws WavemeterException
	{
		return MyPIDControl.setCourse(Channel, Course);
	}
	
	synchronized public CalibrationResult calibrate(int unit, double value, int channel, String Username, String Host ) 
		throws WavemeterException {
		CalibrationResult Result = new CalibrationResult();
		int RawChannel = ChannelManager.calibrationChannelLock(channel);
		System.out.println((new Date()).toString()+" Attempting calibration to WavemeterChannel "
				+ RawChannel + " (0-7) to value " + value + " unit code "+ unit);
		System.out.println("Current switcher channel "+WavemeterLibrary.GetSwitcherChannel(1));
		Result.ReturnValue = WavemeterLibrary.Calibration( WavemeterLibrary.cHeNe633, WavemeterLibrary.cReturnWavelengthVac, 
				value, RawChannel+1 );
		System.out.println((new Date()).toString()+" Calibration returned code "+Result.ReturnValue);
		ChannelManager.calibrationChannelUnlock();
		if (Result.ReturnValue!=0) throw new WavemeterSetException("SetSwitcherMode",Result.ReturnValue);				
		Result.CalibrationDate = new Date(System.currentTimeMillis());
		Result.WavelengthBefore = WavemeterLibrary.GetCalWavelength(0, 0);
		Result.WavelengthAfter = WavemeterLibrary.GetCalWavelength(1, 0);
		Result.Channel = channel;
		Result.UserName = Username;
		Result.HostName = Host;
		++Data.CalibrationResultsSerial;
		return Result;
	}
	
	synchronized public Vector<ChannelRoutingInfo> channelRouting() {
		Vector<ChannelRoutingInfo> Routing = ChannelManager.channelRouting();
		for (int i=0; i<Routing.size(); ++i) {
			ChannelRoutingInfo R = Routing.elementAt(i);
			R.Name = VirtChannel[R.VirtChannel].concatenatedNames();
			R.LockEngaged = Data.ChannelData[R.VirtChannel].LockEnabled;
		}
		return Routing;
	}
	
	/**
	 * Query all data from the WavemeterLibrary. Used at initialization of the object,
	 * and optionally later to ensure we stay in sync with the Wavemeter. Also I do not
	 * know to keep track of the Exposure Mode.
	 */
	synchronized public void queryData()
	{
		SwitcherSignalState State = new SwitcherSignalState();
		synchronized(this) { 
			Data.Temperature = WavemeterLibrary.GetTemperature(0); 
			Data.WideMode = (WavemeterLibrary.GetWideMode(0)==1);
			Data.FastMode = WavemeterLibrary.GetFastMode(false);
			Data.PulseMode = (WavemeterLibrary.GetPulseMode(0)==1);
			Data.SwitcherChannel = WavemeterLibrary.GetSwitcherChannel(0)-1;
			Data.Operation = WavemeterLibrary.GetOperationState(0);
			Data.RegulationEnable = WavemeterLibrary.GetDeviationMode(false);
			Data.SwitcherMode = (WavemeterLibrary.GetSwitcherMode(0)==1);
		}
		// Poll Relevant Data
		for(int i=0; i<NumRawChannels; ++i) {
			synchronized (this) {
				WavemeterLibrary.GetSwitcherSignalStates(i+1, State);
				ChannelManager.setRawChannelState(i, State.Use==1);
				getPID(i);
			}
		}
		LastDataPoll = System.currentTimeMillis();
	}
	
	/**
	 * Receive the current State and measurement results. Also registers that the client requested
	 * the data at this time.
	 * @return Wavemeter results
	 */
	synchronized public WavemeterData getData() 
	{
		LastClientCall = System.currentTimeMillis();
		return Data;
	}
	
	/**
	 * Enable/Disable measurement of a channel
	 * @param Channel
	 * @param Active
	 * @throws WavemeterSetException
	 */
	synchronized public void setActive( int Channel, boolean Active ) throws WavemeterException
	{
		ChannelManager.setChannelActive(Channel,Active);
		if (!Active && Data.ChannelData[Channel].InterlockEnabled) {
			MyPIDControl.unlockPID(Channel);
		}
	}

	/**
	 * Set the auto exposure mode of the given channel.
	 * @param channel
	 * @param mode true for autoexposre false for manual exposure
	 * @throws WavemeterSetException
	 */
	synchronized public void setExposureMode(int channel, boolean mode) throws WavemeterSetException
	{
		MyExposureControl.setExposureMode(channel, mode);
	}

	/**
	 * Set the exposure duration for the given Channel and ccd-array
	 * @param Channel
	 * @param array ccd-array can be 1 or 2
	 * @param Value exposure time in ms
	 * @throws WavemeterSetException
	 */
	synchronized public void setExposure(int Channel, int array, int Value) throws WavemeterSetException
	{
		System.out.println("setExposure channel "+Channel+" array "+array+" value "+Value);
		MyExposureControl.setExposure(Channel, array, Value);
	}	

	/**
	 * Not implemented yet
	 * @param Channel
	 * @param num
	 */
	synchronized public void setAverage(int Channel, int num)
	{
		
	}

	@Override
	synchronized public void setEnable(boolean enable) throws WavemeterSetException {
		setOperationMode( enable?WavemeterLibrary.cCtrlStartMeasurement:WavemeterLibrary.cCtrlStopAll );
	}
	
	/**
	 * Start or Stop the measurement
	 * @param start 
	 * @throws WavemeterSetException
	 */
	synchronized public void setMeasurement(boolean start) throws WavemeterSetException
	{
		setOperationMode( start?WavemeterLibrary.cCtrlStartMeasurement:WavemeterLibrary.cCtrlStopAll );
	}
	
	synchronized public void setOperationMode(int mode) throws WavemeterSetException {
		int result = WavemeterLibrary.Operation( mode);
		if (result!=0) throw new WavemeterSetException("SetOperationMode",result);		
		OperationMode = mode;
	}
	
	/**
	 * Enable/Disable wide mode
	 * @param wide
	 * @throws WavemeterSetException
	 */
	synchronized public void setWideMode(boolean wide) throws WavemeterSetException
	{
		Data.WideMode = wide;
		int result = WavemeterLibrary.SetWideMode(wide?1:0);
		if (result!=0) throw new WavemeterSetException("SetWideMode",result);		
	}
		
	/**
	 * Enable/Disable pulse mode
	 * @param pulsed
	 * @throws WavemeterSetException
	 */
	synchronized public void setPulseMode(boolean pulsed) throws WavemeterSetException
	{
		Data.PulseMode = pulsed;
		int result = WavemeterLibrary.SetPulseMode(pulsed?1:0);
		if (result!=0) throw new WavemeterSetException("SetPulseMode",result);		
	}
	
	/**
	 * Enable/Disable fast mode
	 * @param fast
	 * @throws WavemeterSetException
	 */
	synchronized public void setFastMode(boolean fast) throws WavemeterSetException
	{
		Data.FastMode = fast;
		int result = WavemeterLibrary.SetFastMode(fast); 
		if (result!=0) throw new WavemeterSetException("SetFastMode",result);		
	}
	
	/**
	 * Enable/Disable regulation
	 * @param regulate
	 * @throws WavemeterSetException
	 */
	synchronized public void setRegulationEnable(boolean regulate) throws WavemeterSetException
	{
		Data.RegulationEnable = regulate;
		int result = WavemeterLibrary.SetDeviationMode(regulate);
		if (result!=0) throw new WavemeterSetException("SetDeviationMode",result);		
	}
	
	/**
	 * Enable/Disable switcher mode
	 * @param mode
	 * @throws WavemeterSetException
	 */
	synchronized void setSwitcherMode( boolean mode ) throws WavemeterSetException {
		Data.SwitcherMode = mode;
		int result = WavemeterLibrary.SetSwitcherMode( mode?1:0 );
		if (result!=0) throw new WavemeterSetException("SetSwitcherMode",result);				
	}
	
	/**
	 * Evaluate the wavelength measurement reported by the callback and store the result
	 * in our copy of the WavemeterData
	 * @param EventData
	 * @param Channel
	 * @return always true
	 */
	synchronized private boolean evaluateWavelengthMeasurement( WLMEvent EventData, int Channel ) 
	{
		try {
			//System.out.println("Evaluation");
			int virtchannel = ChannelManager.activeVirtChannel(Channel);
			WavemeterChannelData CurrentVirtualChannel = Data.ChannelData[ virtchannel ];
			CurrentVirtualChannel.Wavelength = EventData.DblVal;
			CurrentVirtualChannel.Timestamp = EventData.IntVal;
			InterlockSettings CurrentInterlock = VirtChannel[virtchannel].getInterlock();
			boolean inRange =  ( EventData.DblVal >= CurrentInterlock.MinWavelength &&  EventData.DblVal <= CurrentInterlock.MaxWavelength);
			if (CurrentVirtualChannel.InterlockEnabled && CurrentVirtualChannel.InterlockInRange != inRange) {
				ChannelManager.pushInterlock(Channel, inRange );
				CurrentVirtualChannel.InterlockInRange = inRange;
			}
			//System.out.println(EventData.DblVal);
			ChannelManager.obtainedResult(virtchannel, CurrentVirtualChannel.Wavelength>0);
			MyPIDControl.measuredValue(virtchannel, EventData.IntVal, CurrentVirtualChannel.Wavelength);
			LastMeasuredChannel = Channel;
			Data.LastResultTime = EventData.IntVal;
			if (ChannelManager.activeChannels()==1) notifyAll();
			try {
				ChannelManager.scheduleChannel(Channel);
			}
			catch (WavemeterException ex) {			
			}
		} catch (Exception ex) {}
		return true;
	} 

	/**
	 * The event loop of the thread which handles all WavemeterLibrary callback events
	 * and stores the results in WavemeterData.
	 * The function enables the callback and handles them. If either no client requested data
	 * for the time ClientCallTimeout or shutdown is requested the precedure disables the
	 * callback and returns.
	 */
	public void run() {
		WLMEvent EventData = new WLMEvent();
		InternalPatternRequest CurrentRequest;
		WavemeterLibrary WavemeterSignalQueue = new WavemeterLibrary();
		int ret = WavemeterSignalQueue.EnableJavaCallback();
		System.out.println((new Date()).toString()+" Enable Callback "+ret);
		System.out.flush(); 
		int NewChannel=0, JavaChannel;
		queryData();
		try {
			setMeasurement(true);
			setSwitcherMode(true);
		} catch (Exception ex) {}
		long loopindex=0;
		int ZeroReturn=0;
		int PosReturn=0;
		while (keepRunning())
		{
			try {
				EventData = WavemeterSignalQueue.poll(1, TimeUnit.SECONDS );
				if (EventData==null) {
					++ZeroReturn;					
				} else {
					++loopindex;
					//if (EventData.Result>0)
					//{
					//System.out.println("Mode: "+EventData.Mode+" Result "+EventData.Result+" Int "+EventData.IntVal+" Dbl "+EventData.DblVal);
					//}
					patternGarbageCollector();

					int Channel;
					synchronized (this) {
						switch (EventData.Mode) {
						case WavemeterLibrary.cmiPulseMode:
							Data.PulseMode = (EventData.IntVal == 1);
							break;
						case WavemeterLibrary.cmiWideMode:
							Data.WideMode = (EventData.IntVal == 1);
							break;
						case WavemeterLibrary.cmiFastMode:
							Data.FastMode = (EventData.IntVal == 1);
							break;
						case WavemeterLibrary.cmiDeviationMode:
							Data.RegulationEnable = (EventData.IntVal == 1);
							break;
						case WavemeterLibrary.cmiExposureMode:
							//System.out.println("Exposure mode: "+EventData.IntVal+" "+EventData.Res1+" "+EventData.Result+" "+EventData.DblVal);
							//System.out.flush();
							break;
						case WavemeterLibrary.cmiOperation:
							Data.Operation = EventData.IntVal;
							break;
						case WavemeterLibrary.cmiSwitcherMode:
							Data.SwitcherMode = (EventData.IntVal == 1);
							break;
						case WavemeterLibrary.cmiSwitcherChannel:
							NewChannel = EventData.IntVal - 1;
							if (NewChannel<=Data.SwitcherChannel ) 
							{ 
								Data.SpinAroundTime = LastSwitching;
								LastSwitching = EventData.Res1;
								notifyAll(); 
							}
							Data.SwitcherChannel = NewChannel;
							//System.out.println("Switcher Channel : "+EventData.IntVal+" "+EventData.Res1);
							//System.out.flush();
							break;
						case WavemeterLibrary.cmiWavelength1:
						case WavemeterLibrary.cmiWavelength2:
							Channel = EventData.Mode - WavemeterLibrary.cmiWavelength1;
							evaluateWavelengthMeasurement(EventData, Channel); 
							break;
						case WavemeterLibrary.cmiWavelength3:
						case WavemeterLibrary.cmiWavelength4:
						case WavemeterLibrary.cmiWavelength5:
						case WavemeterLibrary.cmiWavelength6:
						case WavemeterLibrary.cmiWavelength7:
						case WavemeterLibrary.cmiWavelength8:
							Channel = EventData.Mode
							- WavemeterLibrary.cmiWavelength3 + 2;
							evaluateWavelengthMeasurement(EventData, Channel);
							break;
						case WavemeterLibrary.cmiTemperature:
							Data.Temperature = EventData.DblVal;
							if (EventData.DblVal>0) MyLogTemperature.logTemperature(EventData.DblVal);
							break;
						case WavemeterLibrary.cmiExposureValue11:
							//System.out.println("Exposure");
							Channel = ChannelManager.activeVirtChannel(0);			
							MyExposureControl.evaluateExposure(Channel, 0, EventData.IntVal, loopindex );
							break;
						case WavemeterLibrary.cmiExposureValue12:
						case WavemeterLibrary.cmiExposureValue13:
						case WavemeterLibrary.cmiExposureValue14:
						case WavemeterLibrary.cmiExposureValue15:
						case WavemeterLibrary.cmiExposureValue16:
						case WavemeterLibrary.cmiExposureValue17:
						case WavemeterLibrary.cmiExposureValue18:
							//System.out.println("Exposure");
							Channel = ChannelManager.activeVirtChannel(EventData.Mode-WavemeterLibrary.cmiExposureValue12+1);
							MyExposureControl.evaluateExposure(Channel, 0, EventData.IntVal, loopindex);
							break;
						case WavemeterLibrary.cmiExposureValue21:
							Channel = ChannelManager.activeVirtChannel(0);
							MyExposureControl.evaluateExposure(Channel, 1, EventData.IntVal, loopindex);
							break;
						case WavemeterLibrary.cmiExposureValue22:
						case WavemeterLibrary.cmiExposureValue23:
						case WavemeterLibrary.cmiExposureValue24:
						case WavemeterLibrary.cmiExposureValue25:
						case WavemeterLibrary.cmiExposureValue26:
						case WavemeterLibrary.cmiExposureValue27:
						case WavemeterLibrary.cmiExposureValue28:
							Channel = ChannelManager.activeVirtChannel(EventData.Mode-WavemeterLibrary.cmiExposureValue22+1);
							MyExposureControl.evaluateExposure(Channel, 1, EventData.IntVal, loopindex);
							break;
						case WavemeterLibrary.cmiAnalogOut1:
							Data.ChannelData[(0)].AOVoltage = EventData.DblVal;
							break;
						case WavemeterLibrary.cmiAnalogOut2:
							Data.ChannelData[(1)].AOVoltage = EventData.DblVal;
							break;
						case WavemeterLibrary.cmiAnalogOut3:
						case WavemeterLibrary.cmiAnalogOut4:
						case WavemeterLibrary.cmiAnalogOut5:
						case WavemeterLibrary.cmiAnalogOut6:
						case WavemeterLibrary.cmiAnalogOut7:
						case WavemeterLibrary.cmiAnalogOut8:
							Channel = (EventData.Mode - WavemeterLibrary.cmiAnalogOut3 + 2);
							Data.ChannelData[Channel].AOVoltage = EventData.DblVal;							
							break;
						case WavemeterLibrary.cmiAvg11:
							Data.ChannelData[ChannelManager.activeVirtChannel(0)].Avg1 = EventData.IntVal;	
							break;
						case WavemeterLibrary.cmiAvg12:
						case WavemeterLibrary.cmiAvg13:
						case WavemeterLibrary.cmiAvg14:
						case WavemeterLibrary.cmiAvg15:
						case WavemeterLibrary.cmiAvg16:
						case WavemeterLibrary.cmiAvg17:
						case WavemeterLibrary.cmiAvg18:
							Channel = ChannelManager.activeVirtChannel(EventData.Mode - WavemeterLibrary.cmiAvg12 + 1);
							Data.ChannelData[Channel].Avg1 = EventData.IntVal;
							break;
						case WavemeterLibrary.cmiAvg21:
							Data.ChannelData[ChannelManager.activeVirtChannel(0)].Avg2 = EventData.IntVal;	
							break;
						case WavemeterLibrary.cmiAvg22:
						case WavemeterLibrary.cmiAvg23:
						case WavemeterLibrary.cmiAvg24:
						case WavemeterLibrary.cmiAvg25:
						case WavemeterLibrary.cmiAvg26:
						case WavemeterLibrary.cmiAvg27:
						case WavemeterLibrary.cmiAvg28:
							Channel = ChannelManager.activeVirtChannel(EventData.Mode - WavemeterLibrary.cmiAvg22 +1);
							Data.ChannelData[Channel].Avg2 = EventData.IntVal;	
							break;
						case WavemeterLibrary.cmiMin11:
							Data.ChannelData[ChannelManager.activeVirtChannel(0)].Min1 = EventData.IntVal;	
							break;
						case WavemeterLibrary.cmiMin12:
						case WavemeterLibrary.cmiMin13:
						case WavemeterLibrary.cmiMin14:
						case WavemeterLibrary.cmiMin15:
						case WavemeterLibrary.cmiMin16:
						case WavemeterLibrary.cmiMin17:
						case WavemeterLibrary.cmiMin18:
							Channel = ChannelManager.activeVirtChannel(EventData.Mode - WavemeterLibrary.cmiMin12 + 1);
							Data.ChannelData[Channel].Min1 = EventData.IntVal;
							break;
						case WavemeterLibrary.cmiMin21:
							Data.ChannelData[ChannelManager.activeVirtChannel(0)].Min2 = EventData.IntVal;	
							break;
						case WavemeterLibrary.cmiMin22:
						case WavemeterLibrary.cmiMin23:
						case WavemeterLibrary.cmiMin24:
						case WavemeterLibrary.cmiMin25:
						case WavemeterLibrary.cmiMin26:
						case WavemeterLibrary.cmiMin27:
						case WavemeterLibrary.cmiMin28:
							Channel = ChannelManager.activeVirtChannel(EventData.Mode - WavemeterLibrary.cmiMin22 +1);
							Data.ChannelData[Channel].Min2 = EventData.IntVal;	
							break;
						case WavemeterLibrary.cmiMax11:
							Channel = ChannelManager.activeVirtChannel(0);
							Data.ChannelData[Channel].Max1 = EventData.IntVal;
							MyExposureControl.observedIntensity(Channel, 1, EventData.IntVal, loopindex);
							break;
						case WavemeterLibrary.cmiMax12:
						case WavemeterLibrary.cmiMax13:
						case WavemeterLibrary.cmiMax14:
						case WavemeterLibrary.cmiMax15:
						case WavemeterLibrary.cmiMax16:
						case WavemeterLibrary.cmiMax17:
						case WavemeterLibrary.cmiMax18:
							Channel = ChannelManager.activeVirtChannel(EventData.Mode - WavemeterLibrary.cmiMax12 + 1);
							Data.ChannelData[Channel].Max1 = EventData.IntVal;
							MyExposureControl.observedIntensity(Channel, 1, EventData.IntVal, loopindex);						
							break;
						case WavemeterLibrary.cmiMax21:
							Channel = ChannelManager.activeVirtChannel(0);
							Data.ChannelData[Channel].Max2 = EventData.IntVal;
							MyExposureControl.observedIntensity(Channel, 2, EventData.IntVal, loopindex);	
							break;
						case WavemeterLibrary.cmiMax22:
						case WavemeterLibrary.cmiMax23:
						case WavemeterLibrary.cmiMax24:
						case WavemeterLibrary.cmiMax25:
						case WavemeterLibrary.cmiMax26:
						case WavemeterLibrary.cmiMax27:
						case WavemeterLibrary.cmiMax28:
							Channel = ChannelManager.activeVirtChannel(EventData.Mode - WavemeterLibrary.cmiMax22 +1);
							Data.ChannelData[Channel].Max2 = EventData.IntVal;	
							MyExposureControl.observedIntensity(Channel, 2, EventData.IntVal, loopindex);						
							break;
						case WavemeterLibrary.cmiPatternAnalysisWritten:
							Channel = EventData.IntVal;
							JavaChannel = ChannelManager.activeVirtChannel(Channel-1);
							CurrentRequest = PatternRequestList[JavaChannel];
							if (CurrentRequest.PatternMask!=0 && CurrentRequest.Time<System.currentTimeMillis())
							{
								for (int i=0; i<WavemeterParameters.NumPatterns; ++i)
								{
									if ((CurrentRequest.PatternMask & (1<<i))!=0)
									{
										PatternData Request = new PatternData();
										Request.Channel = Channel;
										Request.Index = i;
										Request.Average = CurrentRequest.AveragePoints;
										PatternData MyPattern = WavemeterLibrary.GetPatternDataNum(Request);
										MyPattern.Channel = JavaChannel;
										LastPatternRead[i] = MyPattern.Timestamp = System.currentTimeMillis();
										PatternMap.put( patternCode(JavaChannel,i) , MyPattern );
										//									System.out.println((new Date()).toString()+" Channel "+Request.Channel+" index "+Request.Index+" size "+Request.Length+" channel "+Request.Average);
									}
								}
								CurrentRequest.PatternMask = 0;
							}
							break;
						case WavemeterLibrary.cmiIntensity:
							//System.out.println((new Date()).toString()+" cmiIntensity : "+EventData.IntVal+ " "+EventData.DblVal+" "+EventData.Res1+" "+EventData.Result+" "+EventData.Ver);
							break;
						case WavemeterLibrary.cmiPower:
							//System.out.println((new Date()).toString()+" cmiPower : "+EventData.IntVal+ " "+EventData.DblVal+" "+EventData.Res1+" "+EventData.Result+" "+EventData.Ver);
							break;
						case WavemeterLibrary.cmiAutoCalMode:
						case WavemeterLibrary.cmiAutoCalPeriod:
						case WavemeterLibrary.cmiAutoCalUnit:
							System.out.println("cmiAutoCal...");
							break;
						case 268:
						case 269:
						case 270:
						case 271:
						case 272:
						case 273:
						case 274:
						case 1024:
							break;
						default:
							System.out.println("Mode: "+EventData.Mode+" "+EventData.IntVal+" "+EventData.DblVal+" "+EventData.Res1);
						 System.out.flush();
						} 
						//System.out.println("Mode: "+EventData.Mode+" "+EventData.IntVal+" "+EventData.DblVal+" "+EventData.Res1);
						//System.out.flush();
					}
					++PosReturn;
				} 
			} catch ( Exception interrupted ) {
				++ZeroReturn;
			}
		}
		WavemeterSignalQueue.DisableJavaCallback();
		System.out.println((new Date()).toString()+" Disable Callback");
		System.out.println("ZeroReturn counter: "+ZeroReturn+" PosReturn counter: "+PosReturn);
		patternGarbageCollector();
		System.out.flush();
		cleanup();
	}
	@Override
	synchronized public void notifyClients() {
		notifyAll();		
	}
	
	synchronized public void raiseChannelEvent(ChannelEvent E) {
		ChannelManager.raiseChannelEvent(E);
	}
}
